﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Windows.Forms;
using System.IO;
using GeoAPI.Geometries;
using SpectationClient.Stuff;
namespace SpectationClient.Stuff {
    
    public class ErrorList {
        public event EventHandler ErrorListIsEmpty;
        public event EventHandler ErrorAdded;

        public enum Relation : byte {
            EQ = 0,
            GE = 1,
            GT = 2,
            LE = 3,
            LT = 4,
            NE = 5
        }

        public Button trigger = null;

        private enum state: byte {
            not_set = 0,
            not_valide = 1,
            valide = 2
        }
        //public delegate void EmptyErrorListEventHandler(object sender, EventArgs e);
        //public delegate void ErrorListFilled(object sender, MouseEventArgs e);

        private List<Object> ERR_LIST = new List<Object>();
        private ErrorProvider EP;
        private NetTopologySuite.IO.WKTReader WKTReader = new NetTopologySuite.IO.WKTReader();
        
        public ErrorList() {
            EP = new ErrorProvider();  
        }
        public ErrorList(ref Button btn) {
            EP = new ErrorProvider();
            trigger = btn;
        }
        public ErrorList(ErrorProvider ep) {
            EP = ep;
        }
        public ErrorList(ErrorProvider ep, ref Button btn) {
            EP = ep;
            trigger = btn;
        }


        public int Count { get { return ERR_LIST.Count; } }
        public int EP_BlinkRate {
            get { return EP.BlinkRate; }
            set { EP.BlinkRate = value; }
        }

        public ErrorBlinkStyle EP_BlinkStyle {
            get { return EP.BlinkStyle; }
            set { EP.BlinkStyle = value;}
        }

        public bool isEmpty{
            get { return ERR_LIST.Count == 0; }
        }

        public void removeAllErrors() {
            for(int i = ERR_LIST.Count - 1; i >= 0; i--) this.removeError(ERR_LIST[i]);
            //foreach(Object o in ERR_LIST){
            //    this.removeError(o);
            //}
        }

        private String getValueString(object sender) {
            Type t_obj = sender.GetType();
            String txt = "";
            if(t_obj == typeof(TextBox)) {
                txt = ((TextBox)sender).Text;
            }else if(t_obj == typeof(ComboBox)) {
                ComboBox cb = (ComboBox)sender;
                Object val = null;
                if(cb.DataSource != null) {
                    val = cb.SelectedValue;
                } else {
                    //Just in case no data appendix is set
                    val = cb.Text;
                }
                //nothing selected? maybee a new value

                txt = (val == null) ? null : val.ToString();
                //txt = String.Format("{0}",val);
                //txt = ((ComboBox)sender).Text;
            }else if(t_obj == typeof(ToolStripTextBox)){
                txt = ((ToolStripTextBox)sender).Text;
            }else if(t_obj.IsSubclassOf(typeof(DataGridViewCell))){
                txt = (String)((DataGridViewCell)sender).Value;
            } else if(t_obj.IsSubclassOf((typeof(Control)))) {
                txt = ((Control)sender).Text;
            } else {
                MessageBox.Show("ErrorList: getValueString(object sender) not implemented for object of type" + t_obj.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }

            if(txt != null) {
                txt = txt.Trim();
                if(txt.Length == 0) txt = null;
            }
            return txt;
        }


        /// <summary>
        /// Validates an object and adds / removes it from the error list
        /// </summary>
        /// <param name="o"></param>
        public bool validation_string(Object sender, bool is_required) {
            String val_str = getValueString(sender);
            state state = state.not_set;
            if(val_str != null && val_str.Trim().Length > 0) state = state.valide;
            return setError(sender, state, is_required, "Bitte Text eingeben", null);
        }

        public bool validation_string(Object sender, int max_length, bool is_required) {
            String val_str = getValueString(sender);
            state state = state.not_set;
            if(val_str != null && val_str.Trim().Length > 0 && val_str.Trim().Length <= max_length) state = state.valide;
            return setError(sender, state, is_required, 
                "Bitte Text eingeben",
                String.Format("Text darf aus maximal {0} Zeichen bestehen",max_length));
        }

        public bool validation_needAtLeastOneValue(List<Object> controls) {
            bool onIsSet = false;
            foreach(Object o in controls) {
                String val_str = getValueString(o);
                onIsSet = onIsSet || val_str != null; 
            }
            if(!onIsSet) {
                foreach(Object o in controls) {
                    setError(o, state.not_set,true, "Bitte Wert angeben", "<if you can see this aks the programmer to fix the problem>");
                }
            }
            return onIsSet;
        }

        public bool validation_dirpath(object sender, bool is_required) {
            String path = getValueString(sender);
            state state = state.valide;

            if(path == null || path.Length == 0) {
                state = state.not_set;
            } else if(! Directory.Exists(path)) state = state.not_valide;

            return setError(sender, state, is_required, "Kein Ordner angegeben", "Ordner existiert nicht");
        }


        public bool validation_filepath(object sender,Object pathObject, bool is_required) {
            state state = state.valide;
            String path = pathObject as String;
            if(path == null || path.Length == 0) {
                state = state.not_set;
            } else if(!File.Exists(path)) state = state.not_valide;

            return setError(sender, state, is_required, "Keine Datei angegeben", "Datei existiert nicht");
        }

        public bool validation_filepath(object sender, bool is_required) {
            String path = getValueString(sender);
            state state = state.valide;

            if(path == null || path.Length == 0) {
                state = state.not_set;
            } else if (!File.Exists(path)) state = state.not_valide;

            return setError(sender, state, is_required, "Keine Datei angegeben", "Datei existiert nicht");
        }

        public bool validation_Int32(object sender, bool is_required) {
            String val_str = getValueString(sender);
            Int32 d;
            state state = state.not_set;
            if(val_str == null || val_str.Length == 0) {
                state = state.not_set;
            } else if(!Int32.TryParse(val_str, out d)) {
                state = state.not_valide;
            } else {
                state = state.valide;
            }
            return setError(sender, state, is_required
                        , "Bitte Zahl eingeben"
                        , "Falscher Wert angegeben/keine Zahl");
        }

        public bool validation_Number(object senderA, Relation relation, object senderB, bool is_required) {
            String val_str_A = getValueString(senderA);
            String val_str_B = getValueString(senderB);
            state stateA = state.not_set;
            state stateB = state.not_set;
            Double dA = -999;
            Double dB = -999;
            System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint;
            System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
            
            if(val_str_A == null || val_str_A.Length == 0) {
                stateA = state.not_set;
            } else if(!Double.TryParse(val_str_A, style, culture, out dA)) {
                stateA = state.not_valide;
            } else {
                stateA = state.valide;
            }
            if(val_str_B == null || val_str_B.Length == 0) {
                stateB = state.not_set;
            } else if(!Double.TryParse(val_str_B, style, culture, out dB)) {
                stateB = state.not_valide;
            } else {
                stateB = state.valide;
            }

            bool retA = false;
            bool retB = false;
            bool is_correct = false;
            if(stateA == state.valide && stateB == state.valide){
                String fail_str_A = null;
                String fail_str_B = null;
                switch(relation){
                    case Relation.EQ: is_correct = dA == dB; fail_str_A = fail_str_B = "falsch"; break;
                    case Relation.GE: is_correct = dA >= dB; fail_str_A = "zu klein"; fail_str_B = "zu groß"; break;
                    case Relation.GT: is_correct = dA >  dB; fail_str_A = "zu klein"; fail_str_B = "zu groß"; break;
                    case Relation.LE: is_correct = dA <= dB; fail_str_A = "zu groß"; fail_str_B = "zu klein"; break;
                    case Relation.LT: is_correct = dA <  dB; fail_str_A = "zu groß"; fail_str_B = "zu klein"; break;
                    case Relation.NE: is_correct = dA != dB; fail_str_A = fail_str_B = "falsch"; break;
                }
                if(!is_correct) stateA = stateB = state.not_valide;
                retA = setError(senderB, stateA, "Wert ist "+fail_str_A);
                retB = setError(senderB, stateB, "Wert ist "+fail_str_B);                
            }else{
                retA = setError(senderB, stateA, is_required
                        , "Bitte Zahl eingeben"
                        , "Falscher Wert angegeben/keine Zahl");
                retB = setError(senderB, stateB, is_required
                        , "Bitte Zahl eingeben"
                        , "Falscher Wert angegeben/keine Zahl");
            }
            return retA && retB;
        }

        public bool validation_Number(object sender, bool is_required) {
            String val_str = getValueString(sender);
            Double d;
            System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint;
            System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
            state state = state.not_set;
            if(val_str == null || val_str.Length == 0) {
                state = state.not_set;
            } else if(!Double.TryParse(val_str,style, culture, out d)) {
                state = state.not_valide;
            } else {
                state = state.valide;
            }
            return setError(sender, state, is_required
                        , "Bitte Zahl eingeben"
                        , "Falscher Wert angegeben/keine Zahl");
        }

        public bool validation_Number(object sender, bool is_required, double min, double max) {
            String val_str = getValueString(sender);
            String msg = "keine Angabe";
            Double d;
            System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint;
            System.Globalization.CultureInfo culture = System.Globalization.CultureInfo.CurrentCulture;
            state state = state.not_set;
            if(val_str == null || val_str.Length == 0) {
                state = state.not_set;
            } else if(!Double.TryParse(val_str, style, culture, out d)) {
                state = state.not_valide;
                msg = "Keine Zahl angegeben";
            } else {
                if(d < min) {
                    msg = String.Format("Wert < {0}", min);
                    state = state.not_valide;
                } else if(d > max) {
                    msg = String.Format("Wert > {0}", max);
                    state = state.not_valide;
                } else {
                    //Console.WriteLine(d);
                    state = state.valide;
                }
            }
            return setError(sender, state, is_required
                        , "Bitte Zahl eingeben"
                        , msg);
        }


        public bool validation_hasValue(TextBox sender, bool isRequired, DataTable dataTable, String valueColumn) {
            state state = state.not_set;
            if(sender.Text != null && sender.Text.Trim().Length > 0) {
                String value = sender.Text.Trim();
                state = DataHelper.hasValue(dataTable.Columns[valueColumn], value) 
                            ? state.valide : state.not_valide;
            
            }
            return setError(sender, state, isRequired, "Angabe fehlt", "Wert existiert nicht");
        }

        public bool validation_hasKeyValue(TextBox sender, bool isRequired, DataTable dataTable, String keyColumn, String valueColumn) {
            state state = state.not_set;
            if(sender.Text != null && sender.Text.Trim().Length > 0) {
                String value = sender.Text.Trim();
                state = DataHelper.hasKeyValue(dataTable, value, keyColumn, valueColumn) 
                            ? state.valide : state.not_valide;
            }
            return setError(sender, state, isRequired, "Angabe fehlt", "Wert existiert nicht");
        }

        public bool validation_DateTime(ref DateTimePicker DTP, bool is_required) {
            DateTime dt = DTP.Value;
            state state = state.not_set;
            if(!DTP.Checked) {
                state = state.not_set;
            } else {
                if(dt == null) { state = state.not_valide; } else { state = state.valide; }
            }
            return setError(DTP, state, is_required, "Bitte Datum angeben", "Falsches Datum"); 
        }


        public enum TimeMode: byte {
            TimeOfDay = 0,
            DayofYear = 1,
            DayAndTime = 2
        }

        public bool validation_checkGeometryWKT(Object sender, bool isRequired, OgcGeometryType geometryType) {
            state state = state.not_set;
            String value = this.getValueString(sender);
            String errortext = "Angabe falsch";
            if(value != String.Empty) {
                try {
                    
                    IGeometry geom =  WKTReader.Read(value);
                    if(geom.OgcGeometryType  == geometryType) {
                        state = state.valide;
                    } else {
                        state = state.not_valide;
                        errortext = String.Format("Kann \"{0}\" nicht in \"{1}\" umwandeln"
                            , value, geometryType.ToString());
                    }
                } catch {
                    state = state.not_valide;
                    errortext = String.Format("\"{0}\" ist keine Well-Known-Text Geometrieangabe", value);
                }
            }
            return this.setError(sender, state, isRequired, "Angabe fehlt", "Angabe falsch");
        }

        public bool validation_checkType(Object sender, bool isRequired, Type requiredType) {
           
            state state = state.not_set;
            String value = getValueString(sender);
            if(value != String.Empty) {
                if(DataHelper.CanConvert(requiredType, value)) {
                    state = state.valide;
                } else {
                    state = state.not_valide;
                }
            }
            return this.setError(sender, state, isRequired,
                "Angabe Fehlt",
                String.Format("Kann \"{0}\" nicht in den Datentyp \"{1}\" umwandeln", value, requiredType.FullName));
        }

        public bool validation_DateBeginEnd(
                  ref DateTimePicker DStart, ref DateTimePicker DEnd, bool is_required, TimeMode timemode) {
            if(!is_required && (!DStart.Checked || !DEnd.Checked)) {
                this.removeError(DEnd);
                this.removeError(DStart);
                return true;
            }
            
            long d1 = 0; 
            long d2 = 0;

            String str_miss =null;
            String str_err = null;
            switch(timemode){
                case TimeMode.DayAndTime: 
                    d1 = DStart.Value.Ticks; 
                    d2 = DEnd.Value.Ticks;
                    str_miss = "Keine Datum-Zeit Gruppe";
                    str_err = "Falsche Datum-Zeit Gruppe";
                    break;
                case TimeMode.DayofYear : 
                    d1 = DStart.Value.Date.Ticks; 
                    d2 = DEnd.Value.Date.Ticks;
                    str_miss = "Keinen Datum angegeben";
                    str_err = "Falsches Datum";
                    break;
                case TimeMode.TimeOfDay : 
                    d1 = DStart.Value.TimeOfDay.Ticks; 
                    d2 = DEnd.Value.TimeOfDay.Ticks;
                    str_miss = "Keine Uhrzeit angegeben";
                    str_err = "Falsche Uhrzeit";
                    break;
            
            }
            //TimeSpan ts1 = dt_begin.TimeOfDay;
            //TimeSpan ts2 = dt_end.TimeOfDay;
            state s1 = state.valide;
            state s2 = state.valide;
            if(d2 < d1) s1 = s2 = state.not_valide;
            if(d1 == 0) s1 = state.not_set;
            if(d2 == 0) s2 = state.not_set;

            bool e1 = setError(DStart, s1, true, str_miss, str_err);
            bool e2 = setError(DEnd, s2, true, str_miss, str_err);
            return e1 && e2;
        }

        /// <summary>
        /// in case the value is not required
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="key"></param>
        /// <param name="error_not_valide"></param>
        /// <returns></returns>
        private bool setError(object sender, state state, String error_not_valide) {
            return setError(sender, state, false, null, error_not_valide);
        }
        
        private bool setError(object sender, state state, bool is_required, String error_not_existing, String error_not_valide) { 
            bool ret = true;
            if (state == state.not_valide){
                    this.addError(sender, error_not_valide); 
                    ret = false;
            }else if(is_required && state == state.not_set){
                    this.addError(sender, error_not_existing);
                    ret = false;
            }else{//not required and not set or not required and valide
                    this.removeError(sender);
                    ret = true;
            }
            return ret;
        }

       
        public void addError(Object obj, String errortext) {
            Type t = obj.GetType();
            if (!ERR_LIST.Contains(obj)) ERR_LIST.Add(obj);
            //aktualisiert oder initialisiert den Error-Text
            if (obj is DataGridViewCell) {
                DataGridViewCell c = (DataGridViewCell)obj;
                c.ErrorText = errortext;
            } else if(obj is DataGridViewRow) {
                DataGridViewRow row = obj as DataGridViewRow;
                row.ErrorText = errortext;
            } else if(obj is GroupBox) {
                GroupBox gb = (GroupBox)obj;
                EP.SetError(gb, errortext);
                EP.SetIconAlignment(gb, ErrorIconAlignment.TopLeft);
            } else if(obj.GetType().IsSubclassOf(typeof(Control))) {
                Control c = (Control)obj;
                EP.SetError(c, errortext);
            } else if(obj is ToolStripTextBox) {
                ToolStripTextBox tb = (ToolStripTextBox)obj;
                EP.SetError(tb.Control, errortext);
            } else {
                throw new Exception("Class: SubShowSelectionTable \nFunction: ERR_LIST_add \nError: no handling for object of type " + obj.GetType());
            }

            if(trigger != null) trigger.Enabled = false;
            if (ErrorAdded != null)ErrorAdded(obj, new EventArgs());
           
        }

        public void checkforDisposedElements() {
            int pos = 0;
            
            while(pos < ERR_LIST.Count){
                if (ERR_LIST[pos] == null) {
                    ERR_LIST.RemoveAt(pos);
                } else {
                    if (ERR_LIST[pos] is DataGridViewCell) {
                        DataGridViewCell C = (DataGridViewCell)ERR_LIST[pos];
                        if (C.RowIndex < 0) ERR_LIST.RemoveAt(pos);
                    } else if(ERR_LIST[pos] is DataGridViewRow) {
                        DataGridViewRow row = ERR_LIST[pos] as DataGridViewRow;
                        if(row.Index < 0) ERR_LIST.RemoveAt(pos);
                    }
                    pos++;
                }
               
            }
            if (ERR_LIST.Count == 0) ErrorListIsEmpty(null, new EventArgs());
        
        }

        public void removeError(Object obj) {
            if (obj == null || obj == DBNull.Value) throw new Exception("Error in using ErrorList");
            Type t = obj.GetType();
            bool wasEmpty = ERR_LIST.Count == 0;

            if (ERR_LIST.Contains(obj)) ERR_LIST.Remove(obj);
            if(obj is DataGridView) {
                DataGridView dgv = (DataGridView)obj;
                foreach(DataGridViewRow row in dgv.Rows) {
                    removeError(row);
                    foreach(DataGridViewCell cell in row.Cells) {
                        removeError(cell);
                    }
                }
                EP.SetError(dgv, "");

            }else if (obj is DataGridViewCell) {
                DataGridViewCell c = (DataGridViewCell)obj;
                c.ErrorText = "";
            } else if(obj is DataGridViewRow){
                DataGridViewRow row = (DataGridViewRow)obj;
                row.ErrorText = "";
            } else if (obj is Control) {
                Control c = (Control)obj;
                EP.SetError(c, "");
            } else if (obj is ToolStripTextBox) {
                ToolStripTextBox tb = (ToolStripTextBox)obj;
                EP.SetError(tb.Control, "");
            } else {
                throw new Exception("Class: SubShowSelectionTable \nFunction: ERR_LIST_remove \nError: no handling for object of type " + obj.GetType());
            }
            
            if (ERR_LIST.Count == 0) {
                if(trigger != null) trigger.Enabled = true;
                if (ErrorListIsEmpty != null && !wasEmpty) ErrorListIsEmpty(obj, new EventArgs());
            }
        }

    }
}
